use serde::Deserialize;
use std::collections::HashMap;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use log::{debug, info, warn};
use thiserror::Error;

#[derive(Debug, Error)]
pub enum RenameError {
    #[error("JSON parsing error: {0}")]
    JsonError(#[from] serde_json::Error),
    #[error("Source path not found or is not a directory: {0}")]
    SourceNotFound(PathBuf),
    #[error("Target directory creation failed for '{0}': {1}")]
    TargetDirectoryCreationFailed(PathBuf, io::Error),
    #[error("Directory read error for '{0}': {1}")]
    DirectoryReadError(PathBuf, io::Error),
    #[error("File copy failed from '{0}' to '{1}': {2}")]
    FileCopyFailed(PathBuf, PathBuf, io::Error),
    #[error("Could not get file type for: {0}")]
    FileTypeUnavailable(PathBuf),
    #[error("Failed to strip prefix from path: {0}")]
    StripPrefixError(PathBuf),
    #[error("Invalid path component: {0}")]
    InvalidPathComponent(String),
    #[error("General I/O error: {0}")]
    IoError(#[from] io::Error),
}

#[derive(Deserialize, Debug)]
struct JsonEntry {
    name: String, // Relative path from source_dir
    children: Vec<JsonEntry>,
}

// Structure to hold pre-processed JSON information for quick lookup
#[derive(Debug, Clone)]
struct JsonItemInfo {
    index: usize,          // Index among its siblings (0-based) for prefixing
    json_name: String,     // The original name from the JSON entry (e.g., "guide/index.md")
    represents_dir: bool, // True if this JSON entry (like "dir/index.md") represents a directory ("dir")
}

// Pre-processes the JSON into a map for efficient lookup.
// The map keys are the *actual* relative source paths (e.g., "guide", "guide/tooling.md")
// and the values are the corresponding JsonItemInfo derived from the JSON entry that references them.
fn build_json_lookup_map(
    entries: &[JsonEntry],
    map: &mut HashMap<PathBuf, JsonItemInfo>,
    is_top_level: bool,
) {
    for (index, entry) in entries.iter().enumerate() {
        let entry_relative_path = PathBuf::from(&entry.name);

        // Determine if this entry represents a directory via the "dir/index.md" convention
        let represents_dir = entry.name.contains('/')
            && entry.name.ends_with("/index.md");
            // We don't check source_path.is_dir() here, assuming JSON is the guide.
            // The copy function will later verify source existence.

        // The key for the map is the path of the item being named/ordered.
        // If represents_dir is true, the key is the dir path (e.g., "guide").
        // Otherwise, it's the file path (e.g., "index.md", "guide/tooling.md").
        let map_key_path = if represents_dir {
            entry_relative_path.parent().unwrap().to_path_buf() // Safe unwrap due to checks above
        } else {
            entry_relative_path.clone()
        };

        // Only insert if not already present (first mention in JSON takes precedence)
        if !map.contains_key(&map_key_path) {
            map.insert(
                map_key_path.clone(), // Use the calculated key path
                JsonItemInfo {
                    index,
                    json_name: entry.name.clone(),
                    represents_dir,
                },
            );
        } else {
             warn!("Duplicate reference in JSON for path '{}' (or its directory). Using first occurrence for ordering/naming.", map_key_path.display());
        }


        // Recursively build map for children *only if* this entry represents a directory structure.
        // Note: The children's 'name' in JSON is absolute relative to source_root,
        // so we pass children directly to the recursive call. The map structure handles the hierarchy implicitly.
        if !entry.children.is_empty() {
            // Even if represents_dir is false (e.g. root index.md with children),
            // we process children based on the JSON structure. But the file itself won't handle them later.
             build_json_lookup_map(&entry.children, map, false);
        }
    }
}


/// Recursively copies content from source to target, applying JSON naming rules.
fn copy_and_rename_recursive(
    current_source_dir: &Path,
    current_target_dir: &Path,
    source_root: &Path, // The absolute path of the original source_dir
    json_lookup: &HashMap<PathBuf, JsonItemInfo>, // Precomputed lookup map
) -> Result<(), RenameError> {
    // Ensure the target directory exists for this level
    fs::create_dir_all(current_target_dir)
        .map_err(|e| RenameError::TargetDirectoryCreationFailed(current_target_dir.to_path_buf(), e))?;

    let entries = fs::read_dir(current_source_dir)
        .map_err(|e| RenameError::DirectoryReadError(current_source_dir.to_path_buf(), e))?;

    for entry_result in entries {
        let entry = entry_result
            .map_err(|e| RenameError::DirectoryReadError(current_source_dir.to_path_buf(), e))?;
        let source_path = entry.path(); // Full path to the current source item

        // Calculate the path relative to the original source_root
        let relative_path = source_path
            .strip_prefix(source_root)
            .map_err(|_| RenameError::StripPrefixError(source_path.clone()))?
            .to_path_buf();

        // Get the original name of the file/directory
        let original_name_os = source_path
            .file_name()
            .ok_or_else(|| RenameError::InvalidPathComponent(source_path.to_string_lossy().into()))?;
        let original_name = original_name_os
            .to_str()
            .ok_or_else(|| RenameError::InvalidPathComponent(original_name_os.to_string_lossy().into()))?;

        // Check the JSON lookup map using the relative path
        let json_info_opt = json_lookup.get(&relative_path);

        // Determine the target name for this item
        let target_name = match json_info_opt {
            Some(info) => {
                // Item is mentioned in JSON, apply naming rules
                if original_name == "index.md" && !info.represents_dir {
                    // It's an actual index.md file referenced directly in JSON (not via dir/index.md)
                    "index.md".to_string()
                } else {
                    // It's a directory, or a non-index.md file mentioned in JSON. Apply prefix.
                    // Index is 0-based in info, prefix starts from 1.
                    format!("{:02}_{}", info.index + 1, original_name)
                }
            }
            None => {
                // Item not in JSON, use its original name
                original_name.to_string()
            }
        };

        // Calculate the full target path
        let target_path = current_target_dir.join(&target_name);

        // Get file type
        let file_type = entry.file_type()
            .map_err(|_| RenameError::FileTypeUnavailable(source_path.clone()))?;

        // Process based on type
        if file_type.is_dir() {
            debug!("Processing Directory: {} -> {}", source_path.display(), target_path.display());
            // Recursively process the directory contents.
            // The target directory itself (`target_path`) will be created by the recursive call.
            copy_and_rename_recursive(&source_path, &target_path, source_root, json_lookup)?;

            // --- Special handling for directories represented by "dir/index.md" in JSON ---
            // If this *source* directory (`relative_path`) was referenced in the JSON map,
            // check if that JSON entry was of the form "dir/index.md" (info.represents_dir).
            if let Some(info) = json_info_opt {
                if info.represents_dir {
                    // This directory (`source_path`) corresponds to a "dir/index.md" JSON entry.
                    // We need to copy the actual source index.md file.
                    let source_index_file_path = source_root.join(&info.json_name); // e.g., source_root/guide/index.md
                    let target_index_file_path = target_path.join("index.md"); // e.g., target_dir/02_guide/index.md

                    if source_index_file_path.is_file() {
                        info!("Copying associated index file: {} -> {}", source_index_file_path.display(), target_index_file_path.display());
                        fs::copy(&source_index_file_path, &target_index_file_path)
                           .map_err(|e| RenameError::FileCopyFailed(source_index_file_path.clone(), target_index_file_path.clone(), e))?;
                    } else {
                         warn!("JSON entry '{}' indicated an index file should exist at '{}', but it was not found. Skipping copy.", info.json_name, source_index_file_path.display());
                    }
                }
            }

        } else if file_type.is_file() {
            // --- Special handling for index.md files ---
            // We need to avoid copying an index.md file if it belongs to a directory
            // that was handled by the 'represents_dir' logic above (because it was already copied).
            let mut should_copy = true;
            if original_name == "index.md" {
                 // Check if the parent directory *relative path* is in the json_lookup
                 if let Some(parent_rel_path) = relative_path.parent() {
                      if let Some(parent_info) = json_lookup.get(parent_rel_path) {
                          // If the parent dir's info in JSON map indicates it represents a dir
                          // AND the json_name matches this index.md's relative path, it means
                          // this index.md was handled by the directory logic.
                          if parent_info.represents_dir && PathBuf::from(&parent_info.json_name) == relative_path {
                               debug!("Skipping copy of already handled associated index file: {}", source_path.display());
                               should_copy = false;
                          }
                      }
                 }
            }

            if should_copy {
                debug!("Copying File: {} -> {}", source_path.display(), target_path.display());
                fs::copy(&source_path, &target_path)
                    .map_err(|e| RenameError::FileCopyFailed(source_path.clone(), target_path.clone(), e))?;
            }

        } else {
            // Handle symlinks, etc. - currently ignored
            warn!("Skipping unsupported file type at: {}", source_path.display());
        }
    }

    Ok(())
}


/// Copies all content from source_dir to target_dir, renaming items based on json rules.
/// Items not mentioned in json are copied with their original names.
///
/// # Arguments
/// * `source_dir` - Path to the source directory.
/// * `target_dir` - Path to the target directory. Will be created if it doesn't exist.
/// * `json_str` - JSON string defining the structure and order for renaming.
///
/// # Returns
/// `Ok(())` on success, or a `RenameError` on failure.
pub fn add_prefix_to_files(source_dir: &str, target_dir: &str, json_str: &str) -> Result<(), RenameError> {
    let source_path = Path::new(source_dir);
    let target_path = Path::new(target_dir);

    // 1. Validate source directory
    if !source_path.is_dir() {
        return Err(RenameError::SourceNotFound(source_path.to_path_buf()));
    }

    // 2. Parse JSON
    debug!("Parsing JSON structure...");
    let entries: Vec<JsonEntry> = serde_json::from_str(json_str)?;

    // 3. Pre-process JSON into a lookup map
    debug!("Building JSON lookup map...");
    let mut json_lookup: HashMap<PathBuf, JsonItemInfo> = HashMap::new();
    build_json_lookup_map(&entries, &mut json_lookup, true);
    debug!("JSON lookup map built: {:?}", json_lookup); // Log the map for debugging

    // 4. Ensure top-level target directory exists
    info!("Ensuring target directory exists: {}", target_path.display());
    fs::create_dir_all(&target_path)
        .map_err(|e| RenameError::TargetDirectoryCreationFailed(target_path.to_path_buf(), e))?;

    // 5. Start the recursive copy and rename process
    info!("Starting recursive copy and rename process...");
    copy_and_rename_recursive(source_path, target_path, source_path, &json_lookup)?;

    info!("Successfully copied and renamed files.");
    Ok(())
}

#[cfg(test)]
mod tests {
    use std::error::Error;

    use super::*;

    #[test]
    fn test() -> Result<(), RenameError> {

        // --- Create Dummy Files/Dirs for Testing ---
        // In a real scenario, these directories and files would already exist.
        //create_dummy_structure("d:/test").unwrap();
        // --- End Dummy Creation ---

        // --- 2. Load and Parse JSON ---
        // Ideally, load from a file:
        // let json_content = fs::read_to_string("path/to/your/structure.json")?;
        // For this example, embed the JSON string:
        let json_content = json_content();

        let source_dir = "d:\\test";
        let target_directory = "d:\\test2"; // <--- CHANGE THIS to your actual directory

        add_prefix_to_files(source_dir, target_directory, &json_content)?;
        println!("\nRename process completed.");

        // --- Cleanup Dummy Files ---
        // println!("\nCleaning up dummy files...");
        // fs::remove_dir_all(target_directory)?;
        // --- End Cleanup ---

        Ok(())
    }

    // --- Helper function to create a dummy file structure for testing ---
    fn create_dummy_structure(base_dir: &str) -> Result<(), Box<dyn Error>> {
        let base_path = Path::new(base_dir);
        if base_path.exists() {
            fs::remove_dir_all(base_path)?; // Clean up previous runs
        }
        fs::create_dir_all(base_path)?;

        let paths_to_create = vec![
            "index.md",
            "getting_started/index.md",
            "guide/index.md",
            "guide/tooling.md",
            "guide/new_app.md",
            "guide/component.md",
            "guide/rsx.md",
            "guide/assets.md",
            "guide/state.md",
            "guide/data_fetching.md",
            "guide/backend.md",
            "guide/databases.md",
            "guide/routing.md",
            "guide/bundle.md",
            "guide/deploy.md",
            "guide/next_steps.md",
            "essentials/index.md",
            "essentials/rsx/index.md",
            "essentials/lifecycle/index.md",
            "essentials/state/index.md",
            "essentials/async/index.md",
            "essentials/breaking/index.md",
            "essentials/error_handling/index.md",
            "guides/index.md",
            "guides/managing_state.md",
            "router/index.md",
            "router/example/index.md",
            "router/example/first-route.md",
            "router/example/building-a-nest.md",
            "router/example/navigation-targets.md",
            "router/example/redirection-perfection.md",
            "router/example/full-code.md",
            "router/reference/index.md",
            "router/reference/routes/index.md",
            "router/reference/routes/nested.md",
            "router/reference/layouts.md",
            "router/reference/navigation/index.md",
            "router/reference/navigation/programmatic.md",
            "router/reference/history-providers.md",
            "router/reference/history-buttons.md",
            "router/reference/routing-update-callback.md",
            "guides/assets.md",
            "guides/web/index.md",
            "guides/desktop/index.md",
            "guides/mobile/index.md",
            "guides/mobile/apis.md",
            "guides/ssr.md",
            "guides/fullstack/index.md",
            "guides/fullstack/hydration.md",
            "guides/fullstack/managing_dependencies.md",
            "guides/fullstack/server_functions.md",
            "guides/fullstack/extractors.md",
            "guides/fullstack/middleware.md",
            "guides/fullstack/authentication.md",
            "guides/fullstack/routing.md",
            "guides/fullstack/static_site_generation.md",
            "cookbook/publishing.md",
            "cookbook/antipatterns.md",
            "cookbook/integrations/index.md",
            "cookbook/integrations/logging.md",
            "cookbook/integrations/internationalization.md",
            "cookbook/state/index.md",
            "cookbook/state/external/index.md",
            "cookbook/state/custom_hooks/index.md",
            "cookbook/bundling.md",
            "cookbook/testing.md",
            "cookbook/tailwind.md",
            "cookbook/optimizing.md",
            "migration/index.md",
            "reference/index.md",
            "reference/hotreload.md",
            "reference/rsx.md",
            "reference/components.md",
            "reference/component_props.md",
            "reference/event_handlers.md",
            "reference/hooks.md",
            "reference/user_input.md",
            "reference/context.md",
            "reference/dynamic_rendering.md",
            "reference/router.md",
            "reference/use_resource.md",
            "reference/use_coroutine.md",
            "reference/spawn.md",
            "contributing/index.md",
            "contributing/project_structure.md",
            "contributing/guiding_principles.md",
            "CLI/index.md",
            "CLI/creating.md",
            "CLI/configure.md",
            "CLI/translate.md",
        ];

        println!("Creating dummy structure in '{}'...", base_dir);
        for rel_path_str in paths_to_create {
            let abs_path = base_path.join(rel_path_str);
            if let Some(parent) = abs_path.parent() {
                fs::create_dir_all(parent)?;
            }
            // Create empty files
            fs::File::create(&abs_path)?;
            // println!("  Created: {}", abs_path.display());
        }
        println!("Dummy structure created.");

        Ok(())
    }

    fn json_content() -> String {
      r#"
  [
    {
      "name": "index.md",
      "children": [
        { "name": "getting_started/index.md", "children": [] }
      ]
    },
    {
      "name": "guide/index.md",
      "children": [
        { "name": "guide/tooling.md", "children": [] },
        { "name": "guide/new_app.md", "children": [] },
        { "name": "guide/component.md", "children": [] },
        { "name": "guide/rsx.md", "children": [] },
        { "name": "guide/assets.md", "children": [] },
        { "name": "guide/state.md", "children": [] },
        { "name": "guide/data_fetching.md", "children": [] },
        { "name": "guide/backend.md", "children": [] },
        { "name": "guide/databases.md", "children": [] },
        { "name": "guide/routing.md", "children": [] },
        { "name": "guide/bundle.md", "children": [] },
        { "name": "guide/deploy.md", "children": [] },
        { "name": "guide/next_steps.md", "children": [] }
      ]
    },
    {
      "name": "essentials/index.md",
      "children": [
        { "name": "essentials/rsx/index.md", "children": [] },
        { "name": "essentials/lifecycle/index.md", "children": [] },
        { "name": "essentials/state/index.md", "children": [] },
        { "name": "essentials/async/index.md", "children": [] },
        { "name": "essentials/breaking/index.md", "children": [] },
        { "name": "essentials/error_handling/index.md", "children": [] }
      ]
    },
    {
      "name": "guides/index.md",
      "children": [
        { "name": "guides/managing_state.md", "children": [] },
        {
          "name": "router/index.md",
          "children": [
            {
              "name": "router/example/index.md",
              "children": [
                { "name": "router/example/first-route.md", "children": [] },
                { "name": "router/example/building-a-nest.md", "children": [] },
                { "name": "router/example/navigation-targets.md", "children": [] },
                { "name": "router/example/redirection-perfection.md", "children": [] },
                { "name": "router/example/full-code.md", "children": [] }
              ]
            },
            {
              "name": "router/reference/index.md",
              "children": [
                { "name": "router/reference/routes/index.md", "children": [] },
                { "name": "router/reference/routes/nested.md", "children": [] },
                { "name": "router/reference/layouts.md", "children": [] },
                { "name": "router/reference/navigation/index.md", "children": [] },
                { "name": "router/reference/navigation/programmatic.md", "children": [] },
                { "name": "router/reference/history-providers.md", "children": [] },
                { "name": "router/reference/history-buttons.md", "children": [] },
                { "name": "router/reference/routing-update-callback.md", "children": [] }
              ]
            }
          ]
        },
        { "name": "guides/assets.md", "children": [] },
        { "name": "guides/web/index.md", "children": [] },
        { "name": "guides/desktop/index.md", "children": [] },
        {
          "name": "guides/mobile/index.md",
          "children": [
            { "name": "guides/mobile/apis.md", "children": [] }
          ]
        },
        { "name": "guides/ssr.md", "children": [] },
        {
          "name": "guides/fullstack/index.md",
          "children": [
            { "name": "guides/fullstack/hydration.md", "children": [] },
            { "name": "guides/fullstack/managing_dependencies.md", "children": [] },
            { "name": "guides/fullstack/server_functions.md", "children": [] },
            { "name": "guides/fullstack/extractors.md", "children": [] },
            { "name": "guides/fullstack/middleware.md", "children": [] },
            { "name": "guides/fullstack/authentication.md", "children": [] },
            { "name": "guides/fullstack/routing.md", "children": [] },
            { "name": "guides/fullstack/static_site_generation.md", "children": [] }
          ]
        },
        { "name": "cookbook/publishing.md", "children": [] },
        { "name": "cookbook/antipatterns.md", "children": [] },
        {
          "name": "cookbook/integrations/index.md",
          "children": [
            { "name": "cookbook/integrations/logging.md", "children": [] },
            { "name": "cookbook/integrations/internationalization.md", "children": [] }
          ]
        },
        {
          "name": "cookbook/state/index.md",
          "children": [
            { "name": "cookbook/state/external/index.md", "children": [] },
            { "name": "cookbook/state/custom_hooks/index.md", "children": [] }
          ]
        },
        { "name": "cookbook/bundling.md", "children": [] },
        { "name": "cookbook/testing.md", "children": [] },
        { "name": "cookbook/tailwind.md", "children": [] },
        { "name": "cookbook/optimizing.md", "children": [] },
        { "name": "migration/index.md", "children": [] }
      ]
    },
    {
      "name": "reference/index.md",
      "children": [
        { "name": "reference/hotreload.md", "children": [] },
        { "name": "reference/rsx.md", "children": [] },
        { "name": "reference/components.md", "children": [] },
        { "name": "reference/component_props.md", "children": [] },
        { "name": "reference/event_handlers.md", "children": [] },
        { "name": "reference/hooks.md", "children": [] },
        { "name": "reference/user_input.md", "children": [] },
        { "name": "reference/context.md", "children": [] },
        { "name": "reference/dynamic_rendering.md", "children": [] },
        { "name": "reference/router.md", "children": [] },
        { "name": "reference/use_resource.md", "children": [] },
        { "name": "reference/use_coroutine.md", "children": [] },
        { "name": "reference/spawn.md", "children": [] }
      ]
    },
    {
      "name": "contributing/index.md",
      "children": [
        { "name": "contributing/project_structure.md", "children": [] },
        { "name": "contributing/guiding_principles.md", "children": [] }
      ]
    },
    {
      "name": "CLI/index.md",
      "children": [
        { "name": "CLI/creating.md", "children": [] },
        { "name": "CLI/configure.md", "children": [] },
        { "name": "CLI/translate.md", "children": [] }
      ]
    }
  ]
  "#.to_string()
    }

    #[test]
    fn test2() -> Result<(), RenameError> {
      // 设置日志记录器 (例如 simple_logger, env_logger) - 可选
      // simple_logger::init_with_level(log::Level::Info).unwrap(); // 例子
  
      let source_directory = "d:\\source_dir"; // 替换为实际源目录
      let target_directory = "d:\\target_dir"; // 替换为实际目标目录
      let json_config = r#"[
          {
            "name": "index.md",
            "children": [
              { "name": "getting_started/index.md", "children": [] }
            ]
          },
          {
            "name": "guide/index.md",
            "children": [
              { "name": "guide/tooling.md", "children": [] },
              { "name": "guide/new_app.md", "children": [] }
            ]
          },
          {
            "name": "essentials/index.md",
            "children": [
              { "name": "essentials/rsx/index.md", "children": [] },
              { "name": "essentials/lifecycle/index.md", "children": [] },
              { "name": "essentials/state/index.md", "children": [] }
            ]
          }
      ]"#;
  
      // 确保在运行前创建了 source_dir 和里面的文件/目录结构
      //setup_source_dir(source_directory); // 伪代码：你需要自己创建测试用的源目录结构
  
      println!("Starting file processing...");
      add_prefix_to_files(source_directory, target_directory, json_config)?;
      println!("File processing completed successfully.");
  
      Ok(())
  }
  
  fn setup_source_dir(base_path: &str) {
      let p = Path::new(base_path);
      fs::create_dir_all(p.join("getting_started")).unwrap();
      fs::write(p.join("getting_started/index.md"), "getting started content").unwrap();
      // ... 创建其他文件和目录 ...
      fs::create_dir_all(p.join("guide")).unwrap();
      fs::write(p.join("guide/tooling.md"), "tooling content").unwrap();
      fs::write(p.join("guide/new_app.md"), "new_app content").unwrap();
      fs::write(p.join("guide/index.md"), "guide index content").unwrap();
      // ... essentials ...
      fs::create_dir_all(p.join("essentials/rsx")).unwrap();
      fs::write(p.join("essentials/rsx/index.md"), "rsx content").unwrap();
      fs::create_dir_all(p.join("essentials/lifecycle")).unwrap();
      fs::write(p.join("essentials/lifecycle/index.md"), "lifecycle content").unwrap();
      fs::create_dir_all(p.join("essentials/state")).unwrap();
      fs::write(p.join("essentials/state/index.md"), "state content").unwrap();
      fs::write(p.join("essentials/index.md"), "essentials index content").unwrap();
      // ... index.md at root ...
      fs::write(p.join("index.md"), "root index content").unwrap();
  }
}
